// * 面向对象编程最重要的原则之一，就是将内部接口与外部接口分隔开来。

// * 内部接口 和 外部接口：
// * 1. 内部接口 —— 可以通过该类的其他方法访问，但不能从外部访问的方法和属性。
// * 2. 外部接口 —— 也可以从类的外部访问的方法和属性。

// * 在 JS 中，有两种类型的对象（属性和方法）：
// * 公共的：可从任何地方访问。它们构成了外部接口。到目前为止，我们只使用了公共的属性和方法。
// * 私有的：只能从类的内部访问。这些用于内部接口。

// ********************************************************************************************************************

// * 受保护的 "waterAmount"。受保护的属性通常以下划线 _ 作为前缀。
class CoffeeMachine {
	_waterAmount = 0 // 内部的水量

	set waterAmount(value) {
		if (value < 0) value = 0
		this._waterAmount = value
	}

	get waterAmount() {
		return this._waterAmount
	}

	constructor(power) {
		this._power = power
		console.log(`Coffee machine created with power ${this.power}`)
	}
}

// 创建咖啡机
let coffeeMachine = new CoffeeMachine(100)

// 加水
coffeeMachine.waterAmount = -10

console.log(coffeeMachine.waterAmount) // 0

// ********************************************************************************************************************

// * 只读的 "power"。让一个属性只读，我们只需设置它的 getter 而不设置它的 setter。
class CoffeeMachine2 {
	constructor(power) {
		this._power = power
	}

	get power() {
		return this._power
	}
}

let coffeeMachine2 = new CoffeeMachine2(100)

console.log(`Power is: ${coffeeMachine2._power}W`) // 功率是100w

coffeeMachine2.power = 25 // 设置无效，没有 setter

// ********************************************************************************************************************

// * 把 get/set 访问器属性，变成 get/set函数。
// * 最终没有严格的规定，而是由你自己来决定用哪种形式。

// 例子：

class C {
	_prop = 0

	// * 访问器属性形式：
	// set prop(value) {}
	// get prop() {}

	// * 函数形式：更灵活，可以接收多个参数。
	setProp(value) {
		this._prop = value
	}

	getProp() {
		return this._prop
	}
}

// ********************************************************************************************************************

// * 私有的 "#waterLimit"。
// * 私有属性和方法应该以 # 作为前缀。它们只在基类的内部可被访问。无法从外部或从继承的类中访问它们。
// * 私有字段不能通过this['name']访问，this['#name'] 不起作用，这是确保私有属性的语法限制。

// * 例子1：
class CoffeeMachine3 {
	// * 私有字段可以和公共字段同时存在
	water = 100
	#water = 200

	#fixWater(value) {
		if (value < 0) return 0
		if (value > this.#water) return this.#water
	}

	setWater(value) {
		this.#water = this.#fixWater(value)
	}
}

let coffee3 = new CoffeeMachine3()

// 不能从类的外部访问类的私有属性和方法
// coffee3.#water // 报错
// coffee3.#fixWater // 报错
// coffee3.water // 100

// *****************

// * 例子2：
class D {
	#prop = 1

	set prop(value) {
		this.#prop = value
	}

	get prop() {
		return this.#prop
	}
}

const d = new D()

console.log(d.prop) // 1

// 继承，不能使用基类的私有属性
class E extends D {
	fn() {
		// console.log(this.#prop) // 报错
		console.log(this.prop) // 1
	}
}

new E().fn()

// ********************************************************************************************************************

// * 总结：
// * 就面向对象而言，内部接口与外部接口的划分被称为封装。

// * 受保护的字段以，_下划线开头。
// * 私有的字段以，#井号开头。
